import Ra

class InvalidActionError(Exception):
    def __init__( self , message ):
	Exception.__init__( self , message )

class Controller:
    def action( self , board , players ):
	'''Return an action of class Draw, Ra or PlayGod that represents what a player is doing
	given the current state of the game'''

    def bid( self , tiles , board_sun , bid_history , forced_to_bid ):
	'''Return a Sun object if bidding on the tiles in play.  Otherwise return None'''

    def set_player( self , player ):
	'''Set the player object for state retrieval'''
	self._player = player

    def get_player( self ):
	'''Get the player object for state retrieval'''
	return self._player

class Validator(Controller):
    def __init__( self , controller ):
	self._controller_to_validate = controller

    def action( self , board , players ):
	action = self._controller_to_validate.action( board , players )
	if isinstance( action , Ra.Draw ) == False and isinstance( action , Ra.Ra ) == False and isinstance( action , Ra.PlayGod ) == False:
	    raise InvalidActionError( 'Action must be a Draw, Ra call or Play God tile' )

	#These methods should raise an InvalidActionError if the move is illegal
	self.is_valid_draw( action , board , players )
	self.is_valid_ra( action , board , players )
	self.is_valid_god( action , board , players )

    def is_valid_draw( self , action , board , players ):
	if isinstance( action , Ra.Draw ) == False:
	    return

	if board.is_full():
	    raise InvalidActionError( "Can't draw on a full board.  In a later version calling Ra will be implicit" )

	return

    def is_valid_ra( self , action , board , players ):
	if isinstance( action , Ra.Ra ) == False:
	    return

	#Is this ever illegal?
	return

    def is_valid_god( self , action , board , players ):
	if isinstance( action , Ra.PlayGod ) == Flase:
	    return

	if len( board ) == 0:
	    raise InvalidActionError( "Can't play a god tile when there are no tiles to take" )

	if action.get_tile_to_take() not in board:
	    raise InvalidActionError( "Can't take a tile not on the board" )

	if len( action._player.gods() ) == 0:
	    raise InvalidActionError( "Can't play a god tile you don't have" )

	return

    def bid( self , tiles , board_sun , bid_history , forced_to_bid ):
	bid = self._controller_to_validate.bid( tiles , board_sun , bid_history , forced_to_bid )
	if bid == None:
	    if forced_to_bid == True:
		raise InvalidActionError( 'Must make a bid because you called Ra and no one else has bid' )
	    return

	#Make sure the player has the sun they are bidding
	for sun in self.get_player().suns():
	    if bid == sun:
		break
	else:
	    raise InvalidActionError( "Must bid a sun in your possession" )

	if bid.usable() == False:
	    raise InvalidActionError( "Must bid a sun that you started this epoch with" )

	if bid_history.was_bidded_on() and bid_history.winning_bid() > bid:
	    raise InvalidActionError( "Must outbid the current high bid" )

	return

class Human(Controller):
    def action( self , board , players ):
	print self.get_player().get_name() + "'s Turn"
	print 'Board:  ' + str( board ).replace( '\n' , '\n\t' )
	print str( self.get_player() ).replace( '\n' , '\n\t' )

	others = ''
	for player in players:
	    if player != self.get_player():
		others += str( player ).replace( '\n' , '\n\t' ) + '\n'
	print others.strip()

	action_str = ''
	while action_str != '1' and action_str != '2' and action_str != '3':
	    print '1)Draw'
	    print '2)Call Ra'
	    print '3)Use God Tile'

	    action_str = raw_input()

	if action_str == '1':
	    return Ra.Draw( self.get_player() )

	if action_str == '2':
	    forced = True
	    if board.is_full():
		forced = False

	    return Ra.Ra( self.get_player() , forced )

	if action_str == '3':
	    return Ra.PlayGod( self.get_player() , self.choose_tile_to_take( board ) )

    def choose_tile_to_take( self , board ):
	tile_to_take = None
	while tile_to_take == None:
	    for idx in xrange( len( board ) ):
		print '%d) %s' % ( idx , str( board[idx] ) )

	    tile_to_take_idx = raw_input()
	    try:
		tile_to_take_idx = int( tile_to_take_idx )
	    except ValueError:
		continue

	    if tile_to_take_idx < 0 or tile_to_take_idx >= len( board ):
		continue

	    tile_to_take = board[ tile_to_take_idx ]

	return tile_to_take

    def bid( self , tiles , board_sun , bid_history , forced_to_bid ):
	print self.get_player().get_name() + ' is bidding'
	print tiles
	print board_sun
	print bid_history
	print 'Forced? ' + str( forced_to_bid )

	bid_str = ''
	suns = self.get_player().suns()
	while self.valid_input( bid_history , bid_str , forced_to_bid ) == False:
	    print '0)Pass'
	    for idx in xrange( len( suns ) ):
		sun = suns[ idx ]
		print '%d)%s' % ( idx + 1 , str( sun ) )

	    bid_str = raw_input()

	if bid_str == '0':
	    return None

	sun_idx = int( bid_str ) - 1
	return suns[ sun_idx ]

    def valid_input( self , bid_history , bid_str , forced_to_bid ):
	try:
	    sun_num = int( bid_str )
	except ValueError:
	    return False

	if sun_num == 0:
	    if forced_to_bid == True:
		return False
	    return True

	sun_num -= 1 #the 0 value is for passing
	if sun_num >= len( self.get_player().suns() ):
	    return False

	sun = self.get_player().suns()[ sun_num ]
	if sun.usable() == False:
	    return False

	if bid_history.was_bidded_on() == False:
	    return True

	if sun < bid_history.winning_bid():
	    return False

	return True

    def choose_to_remove( self , tiles ):
	'''Prompt a player with the choice of which Ra tiles to remove due to a destruction tile.
	   Should only be called when len( tiles ) > 2'''
	ret = set([])
	while len( ret ) < 2:
	    for idx in xrange( len( tiles ) ):
		tile = tiles[idx]
		removed = ''
		if tile in ret:
		    removed = '(Removed)'
		print str( idx ) + ' ' + str( tile ) + removed

	    idx_to_remove = raw_input()
	    try:
		idx_to_remove = int( idx_to_remove )
	    except ValueError:
		print 'Invalid tile to remove'
		continue

	    if idx_to_remove < 0 or idx_to_remove >= len( tiles ):
		print 'Invalid tile to remove'
		continue

	    tile_to_remove = tiles[idx_to_remove]
	    if tile_to_remove in ret:
		print 'Invalid tile to remove'
		continue

	    ret.add( tile_to_remove )

	return ret

    def inform_score( self , player_name , player_score ):
	print player_name + ' scored ' + str( player_score ) + ' points'
	
    def earthquake( self , monuments ):
	if len( monuments ) == 0:
	    return ( None , None )
	if len( monuments ) == 1:
	    return ( monuments[0] , None )
	if len( monuments ) == 2:
	    return monuments

	return self.choose_to_remove( monuments )

    def famine( self , civs ):
	if len( civs ) == 0:
	    return ( None , None )
	if len( civs ) == 1:
	    return ( civs[0] , None )
	if len( civs ) == 2:
	    return civs

	return self.choose_to_remove( civs )
